#include "il2cpp-config.h"
#include "gc/GCHandle.h"
#include "object-internals.h"
#include "GarbageCollector.h"
#include "os/Mutex.h"
#include "utils/Memory.h"
#include <cassert>
#include <memory>

namespace il2cpp
{
namespace gc
{

typedef struct {
	uint32_t  *bitmap;
	void* *entries;
	uint32_t   size;
	uint8_t    type;
	uint32_t     slot_hint : 24; /* starting slot for search */
	/* 2^16 appdomains should be enough for everyone (though I know I'll regret this in 20 years) */
	/* we alloc this only for weak refs, since we can get the domain directly in the other cases */
	uint16_t  *domain_ids;
} HandleData;

/* weak and weak-track arrays will be allocated in malloc memory
 */
static HandleData gc_handles [] = {
	{NULL, NULL, 0, HANDLE_WEAK, 0},
	{NULL, NULL, 0, HANDLE_WEAK_TRACK, 0},
	{NULL, NULL, 0, HANDLE_NORMAL, 0},
	{NULL, NULL, 0, HANDLE_PINNED, 0}
};


static int
find_first_unset (uint32_t bitmap)
{
	int i;
	for (i = 0; i < 32; ++i) {
		if (!(bitmap & (1 << i)))
			return i;
	}
	return -1;
}

static os::FastMutex g_HandlesMutex;

#define lock_handles(handles) g_HandlesMutex.Lock ()
#define unlock_handles(handles) g_HandlesMutex.Unlock ()

static uint32_t
alloc_handle (HandleData *handles, Il2CppObject *obj, bool track)
{
	uint32_t slot;
	int i;
	lock_handles (handles);
	if (!handles->size) {
		handles->size = 32;
		if (handles->type > HANDLE_WEAK_TRACK) {
			handles->entries = (void**)GarbageCollector::AllocateFixed (sizeof (void*) * handles->size, NULL);
		} else {
			handles->entries = (void**)IL2CPP_MALLOC_ZERO (sizeof (void*) * handles->size);
			handles->domain_ids = (uint16_t*)IL2CPP_MALLOC_ZERO (sizeof (uint16_t) * handles->size);
		}
		handles->bitmap = (uint32_t*)IL2CPP_MALLOC_ZERO (handles->size / 8);
	}
	i = -1;
	for (slot = handles->slot_hint; slot < handles->size / 32; ++slot) {
		if (handles->bitmap [slot] != 0xffffffff) {
			i = find_first_unset (handles->bitmap [slot]);
			handles->slot_hint = slot;
			break;
		}
	}
	if (i == -1 && handles->slot_hint != 0) {
		for (slot = 0; slot < handles->slot_hint; ++slot) {
			if (handles->bitmap [slot] != 0xffffffff) {
				i = find_first_unset (handles->bitmap [slot]);
				handles->slot_hint = slot;
				break;
			}
		}
	}
	if (i == -1) {
		uint32_t *new_bitmap;
		uint32_t new_size = handles->size * 2; /* always double: we memset to 0 based on this below */

		/* resize and copy the bitmap */
		new_bitmap = (uint32_t*)IL2CPP_MALLOC_ZERO (new_size / 8);
		memcpy (new_bitmap, handles->bitmap, handles->size / 8);
		IL2CPP_FREE (handles->bitmap);
		handles->bitmap = new_bitmap;

		/* resize and copy the entries */
		if (handles->type > HANDLE_WEAK_TRACK) {
			void* *entries;
			entries = (void**)GarbageCollector::AllocateFixed (sizeof (void*) * new_size, NULL);
			memcpy (entries, handles->entries, sizeof (void*) * handles->size);
			void** previous_entries = handles->entries;
			handles->entries = entries;
			GarbageCollector::FreeFixed (previous_entries);
		} else {
			void* *entries;
			uint16_t *domain_ids;
			domain_ids = (uint16_t*)IL2CPP_MALLOC_ZERO (sizeof (uint16_t) * new_size);
			entries = (void**)IL2CPP_MALLOC (sizeof (void*) * new_size);
			/* we disable GC because we could lose some disappearing link updates */
			GarbageCollector::Disable ();
			memcpy (entries, handles->entries, sizeof (void*) * handles->size);
			memset (entries + handles->size, 0, sizeof (void*) * handles->size);
			memcpy (domain_ids, handles->domain_ids, sizeof (uint16_t) * handles->size);
			for (i = 0; i < (int32_t)handles->size; ++i) {
				Il2CppObject *obj = GarbageCollector::GetWeakLink (&(handles->entries [i]));
				if (handles->entries [i])
					GarbageCollector::RemoveWeakLink (&(handles->entries [i]));
				/*g_print ("reg/unreg entry %d of type %d at %p to object %p (%p), was: %p\n", i, handles->type, &(entries [i]), obj, entries [i], handles->entries [i]);*/
				if (obj) {
					GarbageCollector::AddWeakLink (&(entries [i]), obj, track);
				}
			}
			IL2CPP_FREE (handles->entries);
			IL2CPP_FREE (handles->domain_ids);
			handles->entries = entries;
			handles->domain_ids = domain_ids;
			GarbageCollector::Enable ();
		}

		/* set i and slot to the next free position */
		i = 0;
		slot = (handles->size + 1) / 32;
		handles->slot_hint = handles->size + 1;
		handles->size = new_size;
	}
	handles->bitmap [slot] |= 1 << i;
	slot = slot * 32 + i;
	handles->entries [slot] = obj;
	if (handles->type <= HANDLE_WEAK_TRACK) {
		if (obj)
			GarbageCollector::AddWeakLink (&(handles->entries [slot]), obj, track);
	}

	//mono_perfcounters->gc_num_handles++;
	unlock_handles (handles);
	/*g_print ("allocated entry %d of type %d to object %p (in slot: %p)\n", slot, handles->type, obj, handles->entries [slot]);*/
	return (slot << 3) | (handles->type + 1);
}


uint32_t GCHandle::New (Il2CppObject *obj, bool pinned)
{
	return alloc_handle (&gc_handles [pinned? HANDLE_PINNED: HANDLE_NORMAL], obj, false);
}

uint32_t GCHandle::NewWeakref (Il2CppObject *obj, bool track_resurrection)
{
	uint32_t handle = alloc_handle (&gc_handles [track_resurrection? HANDLE_WEAK_TRACK: HANDLE_WEAK], obj, track_resurrection);

#ifndef HAVE_SGEN_GC
	if (track_resurrection)
		NOT_IMPLEMENTED (GCHandle::NewWeakref);
#endif

	return handle;
}

GCHandleType GCHandle::GetHandleType(uint32_t gchandle)
{
	return static_cast<GCHandleType>((gchandle & 7) - 1);
}

static inline uint32_t GetHandleSlot(uint32_t gchandle)
{
	return gchandle >> 3;
}

Il2CppObject* GCHandle::GetTarget (uint32_t gchandle)
{
	uint32_t slot = GetHandleSlot(gchandle);
	uint32_t type = GetHandleType(gchandle);
	HandleData *handles = &gc_handles [type];
	Il2CppObject *obj = NULL;
	if (type > 3)
		return NULL;
	lock_handles (handles);
	if (slot < handles->size && (handles->bitmap [slot / 32] & (1 << (slot % 32)))) {
		if (handles->type <= HANDLE_WEAK_TRACK) {
			obj = GarbageCollector::GetWeakLink (&handles->entries [slot]);
		} else {
			obj = (Il2CppObject*)handles->entries [slot];
		}
	} else {
		/* print a warning? */
	}
	unlock_handles (handles);
	/*g_print ("get target of entry %d of type %d: %p\n", slot, handles->type, obj);*/
	return obj;
}

static void
il2cpp_gchandle_set_target (uint32_t gchandle, Il2CppObject *obj)
{
	uint32_t slot = GetHandleSlot(gchandle);
	uint32_t type = GCHandle::GetHandleType(gchandle);
	HandleData *handles = &gc_handles [type];
	Il2CppObject *old_obj = NULL;

	if (type > 3)
		return;
	lock_handles (handles);
	if (slot < handles->size && (handles->bitmap [slot / 32] & (1 << (slot % 32)))) {
		if (handles->type <= HANDLE_WEAK_TRACK) {
			old_obj = (Il2CppObject*)handles->entries [slot];
			if (handles->entries [slot])
				GarbageCollector::RemoveWeakLink (&handles->entries [slot]);
			if (obj)
				GarbageCollector::AddWeakLink (&handles->entries [slot], obj, handles->type == HANDLE_WEAK_TRACK);
		} else {
			handles->entries [slot] = obj;
		}
	} else {
		/* print a warning? */
	}
	unlock_handles (handles);

#ifndef HAVE_SGEN_GC
	if (type == HANDLE_WEAK_TRACK)
		NOT_IMPLEMENTED (il2cpp_gchandle_set_target);
#endif
}

void GCHandle::Free (uint32_t gchandle)
{
	uint32_t slot = GetHandleSlot(gchandle);
	uint32_t type = GetHandleType(gchandle);
	HandleData *handles = &gc_handles [type];
	if (type > 3)
		return;
#ifndef HAVE_SGEN_GC
	if (type == HANDLE_WEAK_TRACK)
		NOT_IMPLEMENTED (GCHandle::Free);
#endif

	lock_handles (handles);
	if (slot < handles->size && (handles->bitmap [slot / 32] & (1 << (slot % 32)))) {
		if (handles->type <= HANDLE_WEAK_TRACK) {
			if (handles->entries [slot])
				GarbageCollector::RemoveWeakLink (&handles->entries [slot]);
		} else {
			handles->entries [slot] = NULL;
		}
		handles->bitmap [slot / 32] &= ~(1 << (slot % 32));
	} else {
		/* print a warning? */
	}
	//mono_perfcounters->gc_num_handles--;
	/*g_print ("freed entry %d of type %d\n", slot, handles->type);*/
	unlock_handles (handles);
}

int32_t GCHandle::GetTargetHandle (Il2CppObject * obj, int32_t handle, int32_t type)
{
	if (type == -1)
	{
		il2cpp_gchandle_set_target (handle, obj);
		/* the handle doesn't change */
		return handle;
	}
	switch (type) {
	case HANDLE_WEAK:
		return NewWeakref (obj, false);
	case HANDLE_WEAK_TRACK:
		return NewWeakref (obj, true);
	case HANDLE_NORMAL:
		return New (obj, false);
	case HANDLE_PINNED:
		return New (obj, true);
	default:
		assert (0);
	}
	return 0;
}

void GCHandle::WalkStrongGCHandleTargets(WalkGCHandleTargetsCallback callback, void* context)
{
	lock_handles(handles);
	const GCHandleType types[] = { HANDLE_NORMAL, HANDLE_PINNED };

	for (int gcHandleTypeIndex = 0; gcHandleTypeIndex < 2; gcHandleTypeIndex++)
	{
		const HandleData& handles = gc_handles[types[gcHandleTypeIndex]];

		for (uint32_t i = 0; i < handles.size; i++)
		{
			if (handles.entries[i] != NULL)
				callback(static_cast<Il2CppObject*>(handles.entries[i]), context);
		}
	}
	unlock_handles(handles);
}


} /* gc */
} /* il2cpp */
